{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "crazy-bangladesh",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "\n",
    "import qiskit\n",
    "from qiskit import IBMQ, QuantumCircuit, execute, assemble, schedule, transpile\n",
    "from qiskit.providers.ibmq.job import job_monitor\n",
    "from qiskit.tools.visualization import plot_histogram\n",
    "\n",
    "## Insert IBM Q account token here\n",
    "IBMQ.save_account('', overwrite=True)\n",
    "provider = IBMQ.load_account()\n",
    "## Insert provider details\n",
    "provider = IBMQ.get_provider(hub='', group='', project='')\n",
    "\n",
    "\n",
    "## picked from other script\n",
    "from numpy import pi\n",
    "import scipy\n",
    "import pickle\n",
    "import itertools\n",
    "\n",
    "import qiskit\n",
    "from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister, IBMQ, Aer, execute\n",
    "from qiskit.tools.monitor import job_monitor\n",
    "from qiskit.compiler import transpile, assemble\n",
    "from qiskit.visualization import plot_histogram, plot_circuit_layout\n",
    "from qiskit.transpiler import PassManager\n",
    "from qiskit.ignis.mitigation.measurement import (complete_meas_cal, CompleteMeasFitter)\n",
    "\n",
    "import pprint\n",
    "import copy\n",
    "\n",
    "import networkx as nx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "developing-xerox",
   "metadata": {},
   "outputs": [],
   "source": [
    "## load all my helper functions\n",
    "import sys\n",
    "import importlib\n",
    "import compiler_helper_functions\n",
    "importlib.reload(compiler_helper_functions)\n",
    "from compiler_helper_functions import *\n",
    "import helper_library\n",
    "importlib.reload(helper_library)\n",
    "from helper_library import *\n",
    "import data_logging_functions\n",
    "importlib.reload(data_logging_functions)\n",
    "from data_logging_functions import *\n",
    "import statistics\n",
    "import eval_metrics\n",
    "importlib.reload(eval_metrics)\n",
    "from eval_metrics import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "laden-richardson",
   "metadata": {},
   "outputs": [],
   "source": [
    "## import edited version of Siddhartha's code functions\n",
    "import cnot_circuit_skeleton_dd\n",
    "importlib.reload(cnot_circuit_skeleton_dd)\n",
    "from cnot_circuit_skeleton_dd import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "guided-dinner",
   "metadata": {},
   "outputs": [],
   "source": [
    "def generate_sequence_id_from_combination(combo,sequence_length,rename_list):\n",
    "    \n",
    "    bitstring = [0 for _ in range(sequence_length)]\n",
    "    for i in combo:\n",
    "        for remap_entry in rename_list:\n",
    "            if i == remap_entry[0]: \n",
    "                bitstring[remap_entry[1]] = 1\n",
    "                break\n",
    "    bitstring.reverse()\n",
    "    #print(bitstring)\n",
    "    # reverse the bitstring\n",
    "    sequence_string = ''\n",
    "    for bit in bitstring:\n",
    "        sequence_string = sequence_string + str(bit)\n",
    "    sequence_id = convert_key_to_decimal(sequence_string,len(sequence_string))\n",
    "    #print('Sequence String ', sequence_string, ' Sequence ID ', sequence_id)\n",
    "    return sequence_string, sequence_id\n",
    "    \n",
    "    \n",
    "## function to gather important gate counts from qobj\n",
    "def gather_all_gate_counts_from_op_counts(qobj):\n",
    "    op_counts = qobj.count_ops()\n",
    "    op_keys = ['cx','sx','x','rz','delay','id']\n",
    "    op_count_dict = {}\n",
    "    for op in op_keys:\n",
    "        if op not in list(op_counts.keys()):\n",
    "            curr_op_counts = 0\n",
    "        else:\n",
    "            curr_op_counts = op_counts[op]\n",
    "        op_count_dict[op] = curr_op_counts\n",
    "        \n",
    "    return op_count_dict                                                                                             \n",
    "                         "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "previous-diesel",
   "metadata": {},
   "outputs": [],
   "source": [
    "def run_all_experiments(program,machine):\n",
    "    \n",
    "    ## gather program name for metadata\n",
    "    program_str = program[program.find('evaluation')+11: program.find('.qasm')]\n",
    "    print('Program ', program_str)\n",
    "    \n",
    "    ## params \n",
    "    shots=8192\n",
    "    recur_count=1 # for debugging\n",
    "    repeats = 2 # use 16K shots for smaller benchmarks and 32K shots for the larger workloads\n",
    "    program_qubits = num_qubits_from_qasm(program)\n",
    "    if program_qubits>8:\n",
    "        repeats =4\n",
    "    \n",
    "    #indices of qubits in the device\n",
    "    backend,coupling_map,basis_gates = get_device_information(machine)\n",
    "    qubits = list(range(backend.configuration().to_dict()['n_qubits']))\n",
    "    if len(qubits)>7:\n",
    "        max_circuits_per_batch = 600\n",
    "    else:\n",
    "        max_circuits_per_batch = 75\n",
    "    \n",
    "    all_circuits = []\n",
    "    all_gate_counts = []\n",
    "    \n",
    "    ## read the qasm and generate the quantum circuit\n",
    "    qc = read_qasm(program)\n",
    "    \n",
    "    ## execute on ideal simulator \n",
    "    original_counts = execute_on_ideal_machine([qc],int(shots*repeats))\n",
    "    \n",
    "    ## compile \n",
    "    qc_out,qc_out_cx_cts,qc_out_op_cts = recursive_compile_noise_adaptive(qc,machine,recur_count=recur_count)\n",
    "    qc_out = qc_out[0]\n",
    "    #circuit_depth\n",
    "    circuit_depth = qc_out.depth()\n",
    "    \n",
    "    #total qubit capacity of backend\n",
    "    device = provider.get_backend(machine)\n",
    "    qubits_in_device = len(device.properties().to_dict()['qubits'])\n",
    "\n",
    "    #total number of classical bits used in the circuit\n",
    "    num_clbits = qc_out.num_clbits\n",
    "\n",
    "    #converting circuit to dag\n",
    "    dag = circuit_to_dag(qc_out)\n",
    "    \n",
    "    #get the qubit indices from the DAG\n",
    "    qubit_set = get_qubit_set(dag)\n",
    "    \n",
    "    #create a skeletal instruction table\n",
    "    IDT_Frame=create_empty_InstructionTable(qubit_set=qubit_set, circuit_depth=circuit_depth+10)\n",
    "    \n",
    "    IDT_Frame_p=populate_InstructionTable(qubit_set,dag,IDT_Frame,mode='NotVisual')\n",
    "    \n",
    "    #apply zero filter to the created qubit evolution timestamp table\n",
    "    IDT=zero_filter(IDT_Frame_p)\n",
    "    \n",
    "    ## determine the gate lengths and capture them in a dictionary\n",
    "    cx_lengths,x_lengths,sx_lengths,id_lengths,rz_lengths = get_all_instruction_lengths(machine,provider)\n",
    "    gate_lengths = {'cx': cx_lengths, 'sx': sx_lengths, 'id': id_lengths, 'rz': rz_lengths, 'x': x_lengths}\n",
    "    \n",
    "    #analog Instruction dependency table\n",
    "    analog_IDT = adv_discrete_to_analog(IDT, gate_lengths, mode = 'NotVisual')\n",
    "    \n",
    "    # baseline circuit without DD\n",
    "    total_shots = int(repeats*shots)\n",
    "    baseline_circ = analog_IDT_to_circ(analog_IDT, gate_lengths, qubits_in_device, num_clbits, mode = 'normal')\n",
    "    baseline_counts = execute_on_ideal_machine([baseline_circ],total_shots)\n",
    "    all_circuits.append(baseline_circ)\n",
    "    baseline_ops = gather_all_gate_counts_from_op_counts(baseline_circ)\n",
    "    all_gate_counts.append(baseline_ops)\n",
    "    \n",
    "    # skeleton circuit without DD\n",
    "    skeleton_circ = analog_IDT_to_skeleton_circ(analog_IDT, gate_lengths, qubits_in_device, num_clbits, mode = 'normal')\n",
    "    skeleton_circ_counts = execute_on_ideal_machine([skeleton_circ],total_shots)\n",
    "    all_circuits.append(skeleton_circ)\n",
    "    skeleton_ops = gather_all_gate_counts_from_op_counts(skeleton_circ)\n",
    "    all_gate_counts.append(skeleton_ops)\n",
    "    \n",
    "    ## find all possible DD combinations\n",
    "    #all combinations is a list of all possible combinations of qubits that need to be considered for DD application\n",
    "    qb = [int(i) for i in analog_IDT.columns]\n",
    "    all_combinations = []\n",
    "    rename_table = [(qb[i],i) for i in range(len(qb))]\n",
    "    ## print(rename_table)--> useful to generate sequence ID\n",
    "    \n",
    "    sequence_strings, sequence_ids = [],[]\n",
    "    for r in range(len(qb) + 1):\n",
    "        combinations_object = itertools.combinations(qb, r)\n",
    "        comb_list = list(combinations_object)\n",
    "        all_combinations += comb_list\n",
    "        for curr_combo in comb_list:\n",
    "            seq_str, seq_id = generate_sequence_id_from_combination(curr_combo,len(qb),rename_table)\n",
    "            sequence_strings.append(seq_str)\n",
    "            sequence_ids.append(seq_id)\n",
    "    #removing the first combination (DD on none) since it has already been added\n",
    "    all_combinations = all_combinations[1:]\n",
    "    #print(all_combinations)\n",
    "    #creating all circuits\n",
    "    print('Generating all DD sequences')\n",
    "    for el in all_combinations:\n",
    "    \n",
    "        #creating the circuit for particular DD combination -- XYXY\n",
    "        analog_IDT = adv_discrete_to_analog(IDT, gate_lengths, mode = 'NotVisual')\n",
    "        circ = analog_IDT_to_circ(analog_IDT, gate_lengths, qubits_in_device, num_clbits, qubits_to_consider = list(el), mode = 'xyxy')\n",
    "        all_circuits.append(circ)\n",
    "        op_dicts = gather_all_gate_counts_from_op_counts(circ)\n",
    "        all_gate_counts.append(op_dicts)\n",
    "        \n",
    "        ## Skeleton circuit for same DD combination - XYXY\n",
    "        circ = analog_IDT_to_skeleton_circ(analog_IDT, gate_lengths, qubits_in_device, num_clbits, qubits_to_consider = list(el), mode = 'xyxy')\n",
    "        all_circuits.append(circ)\n",
    "        op_dicts = gather_all_gate_counts_from_op_counts(circ)\n",
    "        all_gate_counts.append(op_dicts)\n",
    "        \n",
    "        ## Actual circuit for IBMQ-DD combination\n",
    "        circ = analog_IDT_to_circ(analog_IDT, gate_lengths, qubits_in_device, num_clbits, qubits_to_consider = list(el), mode = 'ibmq_dd_delay')\n",
    "        all_circuits.append(circ)\n",
    "        op_dicts = gather_all_gate_counts_from_op_counts(circ)\n",
    "        all_gate_counts.append(op_dicts)\n",
    "        \n",
    "        ## Skeleton circuit for IBMQ-DD combination\n",
    "        circ = analog_IDT_to_skeleton_circ(analog_IDT, gate_lengths, qubits_in_device, num_clbits, qubits_to_consider = list(el), mode = 'ibmq_dd_delay')\n",
    "        all_circuits.append(circ)\n",
    "        op_dicts = gather_all_gate_counts_from_op_counts(circ)\n",
    "        all_gate_counts.append(op_dicts)\n",
    "    \n",
    "\n",
    "                \n",
    "    ## execute on the specific machine\n",
    "    print('Real Machine Simulation Step')\n",
    "    counts_vector, job_ids, job_noise_properties, job_time, job_results = execute_on_real_machine(all_circuits, shots=shots, machine_name=machine,max_acceptable_circuits_by_device=max_circuits_per_batch,repeats=repeats)\n",
    "    ## debug \n",
    "    ##counts_vector = execute_on_ideal_machine(all_circuits, shots)\n",
    "    \n",
    "    ## Readjust the counts\n",
    "    print('Readjusting All Dictionaries')\n",
    "    data_dictionary = {}\n",
    "    data_dictionary['program_name'] = program_str\n",
    "    data_dictionary['post_compile_depth'] = circuit_depth\n",
    "    data_dictionary['ideal_counts_baseline'] = original_counts[0]\n",
    "    data_dictionary['ideal_counts_skeleton'] = skeleton_circ_counts[0]\n",
    "    data_dictionary['all_dd_combinations'] = all_combinations\n",
    "    data_dictionary['original_xyxy'] = {}\n",
    "    data_dictionary['skeleton_xyxy'] = {}\n",
    "    data_dictionary['original_ibmq_dd'] = {}\n",
    "    data_dictionary['skeleton_ibmq_dd'] = {}\n",
    "    \n",
    "    ## insert the counts for no DD\n",
    "    data_dictionary['original_xyxy'][0] = {'Sequence_id': sequence_ids[0], 'Sequence ': sequence_strings[0], 'counts': counts_vector[0], 'gate_cost': all_gate_counts[0]}\n",
    "    data_dictionary['skeleton_xyxy'][0] = {'Sequence_id': sequence_ids[0], 'Sequence ': sequence_strings[0], 'counts': counts_vector[1], 'gate_cost': all_gate_counts[1]}\n",
    "    data_dictionary['original_ibmq_dd'][0] = {'Sequence_id': sequence_ids[0], 'Sequence ': sequence_strings[0], 'counts': counts_vector[0], 'gate_cost': all_gate_counts[0]}\n",
    "    data_dictionary['skeleton_ibmq_dd'][0] = {'Sequence_id': sequence_ids[0], 'Sequence ': sequence_strings[0], 'counts': counts_vector[1], 'gate_cost': all_gate_counts[1]}\n",
    "    \n",
    "    base_index = 2\n",
    "    for dd_combination_id in range(1,len(all_combinations)+1): # shift by 1 to accomodate no dd case from getting overwritten\n",
    "        data_dictionary['original_xyxy'][dd_combination_id] = {'Sequence_id': sequence_ids[dd_combination_id], 'Sequence ': sequence_strings[dd_combination_id], 'counts': counts_vector[base_index], 'gate_cost': all_gate_counts[base_index]}\n",
    "        data_dictionary['skeleton_xyxy'][dd_combination_id] = {'Sequence_id': sequence_ids[dd_combination_id], 'Sequence ': sequence_strings[dd_combination_id], 'counts': counts_vector[base_index+1], 'gate_cost': all_gate_counts[base_index+1]}\n",
    "        data_dictionary['original_ibmq_dd'][dd_combination_id] = {'Sequence_id': sequence_ids[dd_combination_id], 'Sequence ': sequence_strings[dd_combination_id], 'counts': counts_vector[base_index+2], 'gate_cost': all_gate_counts[base_index+2]}\n",
    "        data_dictionary['skeleton_ibmq_dd'][dd_combination_id] = {'Sequence_id': sequence_ids[dd_combination_id], 'Sequence ': sequence_strings[dd_combination_id], 'counts': counts_vector[base_index+3], 'gate_cost': all_gate_counts[base_index+3]}\n",
    "        ### move to the next set \n",
    "        base_index = base_index+4 \n",
    "    #print(data_dictionary)\n",
    "    # for debugging only return data_dictionary return all_circuits\n",
    "         \n",
    "    return data_dictionary, all_circuits, job_ids, job_noise_properties, job_time, job_results\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "decimal-actor",
   "metadata": {},
   "outputs": [],
   "source": [
    "## run the simulations for a batch overnight\n",
    "\n",
    "import glob\n",
    "import os\n",
    "import re\n",
    "\n",
    "files = '../benchmarks/' + '*.qasm'\n",
    "filelist =  glob.glob(files)\n",
    "#print(filelist)\n",
    "for program in filelist:\n",
    "    print('Program ', program)\n",
    "    machine_name = 'ibmq_guadalupe'\n",
    "    program_str = '_'+ program[program.find('benchmarks')+11: program.find('.qasm')] + '_'\n",
    "    pattern = re.compile(machine_name+program_str)\n",
    "    ## check if it is possible to even simulate the program at all (number of qubits in program <= number of qubits on the device)\n",
    "    num_qubits_in_program = num_qubits_from_qasm(program)\n",
    "    backend,_,_ = get_device_information(machine_name)\n",
    "    num_qubits_on_device = backend.configuration().to_dict()['n_qubits']\n",
    "    if num_qubits_in_program<=num_qubits_on_device:\n",
    "        simulate_bit = 1\n",
    "    else:\n",
    "        simulate_bit = 0\n",
    "    for filepath in os.listdir('../logfiles/'):\n",
    "        if pattern.match(filepath):\n",
    "            #do stuff with matching file\n",
    "            print('File exists')\n",
    "            simulate_bit = 0\n",
    "            break\n",
    "    if simulate_bit==1:\n",
    "        print('Simulate')\n",
    "        char_output, post_compile_circuits,job_ids, job_noise_properties, job_time, job_results  = run_all_experiments(program,machine_name)\n",
    "        currentDT = datetime.datetime.now()\n",
    "        datestring = str(currentDT.month) + '_' + str(currentDT.day) + '_' + str(currentDT.hour)\n",
    "\n",
    "        logname = '../logfiles/'+machine_name+program_str+datestring+'.log'\n",
    "        picklename = '../pickle_dumps/'+machine_name+program_str+datestring+'.pkl'\n",
    "\n",
    "        ## dump all the information into logfile and pickle file\n",
    "        write_data_dictionary_into_logfile(char_output,outputfname=logname)\n",
    "        dump_objects_into_pickle_file(picklename,post_compile_circuits,job_ids,job_noise_properties,job_time,job_results)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "finite-kernel",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
